#include <string.h>
#include <jni.h>
#include <android/log.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <linux/input.h>
#include <linux/input-event-codes.h>

#include <sys/inotify.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <pthread.h>

//打印日志 LOGI("failed to open file: %s %d", "string", 0);
#define  TAG    "ForceFeedback"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG,__VA_ARGS__)

//const char *DeviceIdG29 = "946d:c24f";//设备id: Logitech G29 PS3模式
char curDeviceId[10];//当前设备id 046d:c24f
char curDeviceFilePath[20];//当前设备文件地址 dev/input/event0
int curFd = 0;//当前设备文件描述符 >0时表示已连接设备
struct ff_effect ffConstant;//常力效果
/////////////////////////////////////多线程//////////////////////////////////////
JavaVM *gJavaVM = NULL;//全局 JavaVM 变量, 用于获取环境变量env
jobject gJavaObj = NULL;//全局 Jobject 变量, 用于存储jobject thiz

//函数抽离，仅保留java层接口 TODO
//写在头文件里
int checkDeviceFile(const char *filePath);

void *read_thread_exec(void *arg);

int setAutoCenter(float value);

int openConstant();

int closeConstant();

/**
 * 查找设备,更新当前设备
 * 遍历文件=>读取id=>找到指定设备
 * @return -1未找到 1已找到
 */
JNIEXPORT jint JNICALL
Java_org_freedesktop_forcefeedback_ForceFeedback_findDevice(JNIEnv *env, jobject thiz) {
    char *dirPath = "/dev/input/";
    DIR *dir = opendir(dirPath);
    if (dir == NULL) {
        LOGE("find device, opendir failed");
        return -1;
    }
    //清空当前设备信息
    strcpy(curDeviceId, "");
    strcpy(curDeviceFilePath, "");
    //遍历文件夹下的内容, 查找指定设备
    int findResult = -1;//查找结果
    struct dirent *file;//子文件
    while ((file = readdir(dir)) != NULL) {
        //过滤掉"."(当前目录)和".."(上一级)
        if (strcmp(file->d_name, ".") == 0 || strcmp(file->d_name, "..") == 0) {
            continue;
        }
        char childPath[30];
        strcpy(childPath, dirPath);
        strcat(childPath, file->d_name);
        //查找指定设备，更新当前设备信息
        findResult = checkDeviceFile(childPath);
        if (findResult == 1) {
            break;
        }
    }
    closedir(dir);
    return findResult;
}

/**
 * 连接并读取当前设备
 * 打开当前设备文件=>设置参数_=>持续读取
 * @return -1失败 1成功
 */
JNIEXPORT jint JNICALL
Java_org_freedesktop_forcefeedback_ForceFeedback_connectAndReadDevice(JNIEnv *env, jobject thiz) {
    char *filePath = curDeviceFilePath;
    if (filePath == NULL || strlen(filePath) <= 0) {
        LOGE("get device file path failed: %s", curDeviceFilePath);
        return -1;
    }
    //打开文件
    curFd = open(filePath, O_RDWR);
    if (curFd <= 0) {
        LOGE("connect device failed, path=%s", filePath);
        return -1;
    } else {
        LOGI("connect device, path=%s", filePath);// dev/input/event0
    }
    //设置回中力
    setAutoCenter(0.5f);
    //上传力反馈效果-常力
    memset(&ffConstant, 0, sizeof(ffConstant));//初始化为0
    ffConstant.type = FF_CONSTANT;
    ffConstant.id = -1;
    ffConstant.direction = 0xC000;//向左
    /*0 deg -> 0x0000（下）
    90 deg -> 0x4000（左）
    180 deg -> 0x8000（上）
    270 deg -> 0xC000（右）*/
    if (ioctl(curFd, EVIOCSFF, &ffConstant) < 0) {//将效果上传到设备
        LOGE("upload effect failed");
    }
    //其它力反馈效果
    //type: 效果类型
    //  FF_CONSTANT, FF_PERIODIC, FF_RAMP, FF_SPRING, FF_FRICTION, FF_DAMPER, FF_RUMBLE, FF_INERTIA, or FF_CUSTOM)
    //direction: 效果方向
    //trigger 触发条件
    //replay 效果的调度
    //u 效果特定的结构,进一步定义效果参数
    //设置灯光


    //回调函数
    jclass class = (*env)->GetObjectClass(env, thiz);// jclass 类型是一个局部引用
    if (class == NULL) {
        return -1;
    }
    jmethodID methodId = (*env)->GetMethodID(env, class, "readInputEvent", "(III)V");
    if (methodId == NULL) {
        (*env)->DeleteLocalRef(env, class);
        return -1;
    }
    (*env)->DeleteLocalRef(env, class);
    //(*env)->CallVoidMethod(env, gJavaObj, methodId, 1, 2, 3);//回调函数


    //创建一个读取线程
    if (gJavaVM == NULL) {
        (*env)->GetJavaVM(env, &gJavaVM);//保存全局 JavaVM，注意 JavaVM 不是 JNI 引用类型
    }
    if (gJavaObj == NULL) {
        gJavaObj = (*env)->NewGlobalRef(env, thiz);//创建全局引用，方便其他函数或线程使用
    }
    pthread_t read_thread;
    if (pthread_create(&read_thread, NULL, &read_thread_exec, methodId) != 0) {
        LOGE("cannot create read thread");
        return -1;
    }

//    //回调函数
//    jclass class = (*env)->GetObjectClass(env, thiz);// jclass 类型是一个局部引用
//    if (class == NULL) {
//        return -1;
//    }
//    jmethodID methodId = (*env)->GetMethodID(env, class, "readInputEvent", "(III)V");
//    if (methodId == NULL) {
//        return -1;
//    }
//    (*env)->DeleteLocalRef(env, class);
//
//    //(*env)->CallVoidMethod(env, thiz, methodId, 1, 2, 3);
//    //持续读取
//    LOGI("connect device, start read");
//    struct input_event event;
//    while (1) {
//        if (read(curFd, &event, sizeof(event)) == sizeof(event)) {
//            //EV_SYN 用作分隔事件的标记
//            //EV_MSC 用于描述不适合其他类型的各种输入数据
//            if (event.type != EV_SYN && event.type != EV_MSC) {
//                (*env)->CallVoidMethod(env, thiz, methodId, event.type, event.code, event.value);
//            }
//        } else {
//            LOGE("connect device, read error, fd = %d", curFd);
//            curFd = 0;
//            break;
//        }
//    }
//    LOGI("connect device, finish read");
    return 1;
}

/**
 * 断开连接
 * 停止读取=>清空设置=>关闭文件
 * @return -1失败 1成功
 */
JNIEXPORT jint JNICALL
Java_org_freedesktop_forcefeedback_ForceFeedback_disconnectDevice(JNIEnv *env, jobject thiz) {
    if (curFd <= 0) {
        return -1;
    }
    LOGI("disconnect device, fd=%d", curFd);
    //删除力反馈效果-常力
    if (ioctl(curFd, EVIOCRMFF, ffConstant.id) < 0) {
        LOGE("remove effect failed");
    }
    //关闭文件
    close(curFd);
    curFd = 0;
    return 1;
}

/**
 * 获取当前设备id
 * @return 例如：046d:c24f
 */
JNIEXPORT jstring JNICALL
Java_org_freedesktop_forcefeedback_ForceFeedback_getDeviceId(JNIEnv *env, jobject thiz) {
    return (*env)->NewStringUTF(env, curDeviceId);
}

/**
 * 获取当前设备文件描述符
 * @return 例如：23
 */
JNIEXPORT jint JNICALL
Java_org_freedesktop_forcefeedback_ForceFeedback_getDeviceFd(JNIEnv *env, jobject thiz) {
    return curFd;
}

/**
 * 设置回中力
 * @param jValue ∈[0,1]
 * @return -1失败 1成功
 */
JNIEXPORT jint JNICALL
Java_org_freedesktop_forcefeedback_ForceFeedback_setAutoCenter(JNIEnv *env, jobject thiz,
                                                               jfloat jValue) {
    return setAutoCenter(jValue);
}

/**
 * 设置常力
 * @param jValue ∈[-1,1]
 * @return -1失败 1成功
 */
JNIEXPORT jint JNICALL
Java_org_freedesktop_forcefeedback_ForceFeedback_setConstant(JNIEnv *env, jobject thiz,
                                                             jfloat jValue) {
    if (curFd <= 0) {
        return -1;
    }
    if (ffConstant.id < 0) {
        return -1;
    }
    if (jValue > -0.01 & jValue < 0.01) {//开启或关闭常力
        closeConstant();
        return 1;
    } else {
        openConstant();
    }
    //常力∈[-32768,32767] 0x7fff=32767
    ffConstant.u.constant.level = 0x7fff * jValue;//[-1,1] => [-32767,32767]
    if (ioctl(curFd, EVIOCSFF, &ffConstant) < 0) {
        LOGE("update effect failed");
        return -1;
    } else {
        LOGI("set constant, value=%f", jValue);
    }
    return 1;
}

////////////////////////////////////测试/////////////////////////////////////////
/**
 * 设置增益
 * @param jValue ∈[0,1]
 * @return
 */
JNIEXPORT jint JNICALL
Java_org_freedesktop_forcefeedback_ForceFeedback_setGain(JNIEnv *env, jobject thiz,
                                                         jfloat jValue) {
    if (curFd <= 0) {
        return -1;
    }
    //回中力∈[0,65536] 0xffff=65536
    struct input_event event;
    event.type = EV_FF;
    event.code = FF_GAIN;
    event.value = 0xFFFFUL * jValue;//[0,1] => [0,65536]
    if (write(curFd, &event, sizeof(event)) < 0) {
        LOGE("set gain failed");
        return -1;
    } else {
        LOGI("set gain, vale=%f", jValue);
    }
    return 1;
}

//查询设备能力相关
/* Number of bits for 1 unsigned char */
#define nBitsPerUchar          (sizeof(unsigned char) * 8)
/* Index=Offset of given bit in 1 unsigned char */
#define bitOffsetInUchar(bit)  ((bit)%nBitsPerUchar)
/* Index=Offset of the unsigned char associated to the bit
   at the given index=offset */
#define ucharIndexForBit(bit)  ((bit)/nBitsPerUchar)
/* Test the bit with given index=offset in an unsigned char array */
#define testBit(bit, array)    ((array[ucharIndexForBit(bit)] >> bitOffsetInUchar(bit)) & 1)

/**
 * 查询设备能力
 */
JNIEXPORT jint JNICALL
Java_org_freedesktop_forcefeedback_ForceFeedback_queryDeviceCapabilities(JNIEnv *env,
                                                                         jobject thiz) {
    //同时支持的效果数
    int n_effects;
    if (ioctl(curFd, EVIOCGEFFECTS, &n_effects) < 0) {
        LOGE("query number of effects failed");
    }
    LOGI("Maximum number of simultaneous effects: %d", n_effects);

    //查询设备能力
    unsigned char ffFeatures[1 + FF_MAX / 8 / sizeof(unsigned char)];//设备能力
    memset(ffFeatures, 0, sizeof(ffFeatures) * sizeof(unsigned char));
    if (ioctl(curFd, EVIOCGBIT(EV_FF, sizeof(ffFeatures) * sizeof(unsigned char)), ffFeatures) <
        0) {
        LOGE("query force feedback features failed");
        return -1;
    }

    jboolean support;
    support = testBit(FF_CONSTANT, ffFeatures);//渲染恒力效果 ok
    LOGI("FF_CONSTANT is supported %d", support);
    support = testBit(FF_PERIODIC, ffFeatures);//波形渲染周期性效果
    LOGI("FF_PERIODIC is supported %d", support);
    support = testBit(FF_RAMP, ffFeatures);//渲染斜坡效果
    LOGI("FF_RAMP is supported %d", support);
    support = testBit(FF_SPRING, ffFeatures);//模拟弹簧的存在
    LOGI("FF_SPRING is supported %d", support);
    support = testBit(FF_FRICTION, ffFeatures);//模拟摩擦力 no?
    LOGI("FF_FRICTION is supported %d", support);
    support = testBit(FF_DAMPER, ffFeatures);//模拟阻尼效果
    LOGI("FF_DAMPER is supported %d", support);
    support = testBit(FF_RUMBLE, ffFeatures);//隆隆声效果
    LOGI("FF_RUMBLE is supported %d", support);
    support = testBit(FF_INERTIA, ffFeatures);//模拟惯性
    LOGI("FF_INERTIA is supported %d", support);
    support = testBit(FF_GAIN, ffFeatures);//增益可调 ok
    LOGI("FF_GAIN is supported %d", support);
    support = testBit(FF_AUTOCENTER, ffFeatures);//自动居中可调 ok
    LOGI("FF_AUTOCENTER is supported %d", support);

    return 0;
}
///////////////////////////////////内部函数//////////////////////////////////////
/**
 * 检查单个设备
 * 判断是否指定设备，更新当前设备信息
 * @param filePath
 * @return -1未找到 1已找到
 */
int checkDeviceFile(const char *filePath) {
    //打开文件
    int fd = open(filePath, O_RDWR);
    if (fd < 0) {
        LOGE("open device file failed: %s", filePath);
        return -1;
    }
    //读取设备名称
    char deviceName[80];
    if (ioctl(fd, EVIOCGNAME(sizeof(deviceName) - 1), &deviceName) < 0) {
        LOGE("get device name failed: %s", filePath);
    } else {
        deviceName[sizeof(deviceName) - 1] = '\0';//字符串结束标志
        //LOGI("find device, name=%s", deviceName);//Logitech G29 Driving Force Racing Wheel
    }
    //读取设备节点信息
    struct input_id inputId;
    if (ioctl(fd, EVIOCGID, &inputId) < 0) {
        LOGE("get input id failed: %s", filePath);
        close(fd);
        return -1;
    }
    close(fd);
    //设备id
    char deviceId[10];
    sprintf(deviceId, "%04x:%04x", inputId.vendor, inputId.product);
    LOGI("find device, file=%s, id=%s, name=%s", filePath, deviceId,
         deviceName);//Logitech G29 Driving Force Racing Wheel
    /*if (strcmp(deviceId, DeviceIdG29) != 0) {
        return -1;
    }*/
    //名字中包含 Wheel
    if (strstr(deviceName, "Wheel") == NULL) {
        return -1;
    }
    strcpy(curDeviceId, deviceId);
    strcpy(curDeviceFilePath, filePath);
    LOGI("find device, id=%s", deviceId);
    return 1;
}

//读取线程
void *read_thread_exec(void *method) {
    LOGI("read thread start, thread id=%ld", pthread_self());

    jmethodID methodId = (jmethodID) method;
    //从全局的JavaVM中获取到环境变量
    JNIEnv *env;
    (*gJavaVM)->AttachCurrentThread(gJavaVM, &env, NULL);
    //开始读取
    struct input_event event;
    while (curFd > 0) {
        if (read(curFd, &event, sizeof(event)) == sizeof(event)) {
            //EV_SYN 用作分隔事件的标记
            //EV_MSC 用于描述不适合其他类型的各种输入数据
            if (event.type != EV_SYN && event.type != EV_MSC) {
                (*env)->CallVoidMethod(env, gJavaObj, methodId, event.type, event.code, event.value);
            }
        } else {
            LOGE("connect device, read error, fd = %d", curFd);
            curFd = 0;
            break;
        }
    }
    LOGI("read thread finish");

    (*gJavaVM)->DetachCurrentThread(gJavaVM);
    return NULL;
}

/**
 * 设置回中力
 * @param value ∈[0,1]
 * @return -1失败 1成功
 */
int setAutoCenter(float value) {
    if (curFd <= 0) {
        return -1;
    }
    //回中力∈[0,65536] 0xffff=65536
    struct input_event event;
    event.type = EV_FF;
    event.code = FF_AUTOCENTER;
    event.value = 0xFFFFUL * value;//[0,1] => [0,65536]
    if (write(curFd, &event, sizeof(event)) < 0) {
        LOGE("set autocenter failed");
        return -1;
    } else {
        LOGI("set autocenter, vale=%f", value);
    }
    return 1;
}

/**
 * 开启常力
 * @return -1失败 1成功
 */
int openConstant() {
    if (curFd <= 0) {
        return -1;
    }
    struct input_event event;
    event.type = EV_FF;
    event.code = ffConstant.id;
    event.value = 1;
    if (write(curFd, &event, sizeof(event)) < 0) {
        LOGE("open constant failed");
        return -1;
    }
    return 1;
}

/**
 * 关闭常力
 * @return -1失败 1成功
 */
int closeConstant() {
    if (curFd <= 0) {
        return -1;
    }
    struct input_event event;
    event.type = EV_FF;
    event.code = ffConstant.id;
    event.value = 0;
    if (write(curFd, &event, sizeof(event)) < 0) {
        LOGE("close constant failed");
        return -1;
    }
    return 1;
}